#pragma once

// Copyright (c) 2010 Manuel Peinado Gallego <manuel.peinado@gmail.com>
// Distributed under the MIT License

// Mat3.h : interface of the Mat3 class
//


#include "alg3d/Quat.h"
#include <iostream>

namespace gx
{

// --------------------------------------------------------------
// "Mat3" class
// --------------------------------------------------------------
// Represents a SR (scaling+rotation) matrix. 
// We use column vectors, which implies that columns in the 
// matrix are the axes of the coordinate system represented by 
// this matrix. It also implies that multiplication by a vector 
// is R*v and that composition is in reverse order (R1 * R2 
// means "first transform by R2 and then by R1)". This is the 
// convention used by OpenGL, but be careful because most 
// textbooks (and DirectX) use the row major order. 
// Fortunately, we can convert between the two orders with the 
// transpose.
// A valid SR matrix is one for which col_i * col_j = 0 for 
// i != j. If you have a scaling matrix S=(sx,sy,sz)*I and
// a rotation matrix R, an SR matrix is formed ar SR = R * S
// --------------------------------------------------------------

class Mat3
{
public:

    // ----------------------------------------------------------
    // Create an unitialized matrix
    // ----------------------------------------------------------

    Mat3(); 

    // ----------------------------------------------------------
    // Create a matrix from its individual components. The 
    // arguments should represent a valid SR matrix, 
    // ----------------------------------------------------------

    Mat3(double m00, double m01, double m02,
         double m10, double m11, double m12, 
         double m20, double m21, double m22);

    // ----------------------------------------------------------
    // Create an identity matrix. Example of use:
    //   gx::Mat3 m(gx::eye);
    // ----------------------------------------------------------

    explicit Mat3(Eye);

    // ----------------------------------------------------------
    // Create a matrix from its individual columns. The 
    // arguments should represent a valid SR matrix
    // ----------------------------------------------------------
    
    Mat3(const Vec3 &I, const Vec3 &J, const Vec3 &K);

    // ----------------------------------------------------------
    // Access individual colums from a matrix
    // ----------------------------------------------------------

    Vec3 get_col_0() const;
    Vec3 get_col_1() const;
    Vec3 get_col_2() const;

    // ----------------------------------------------------------
    // Indexed acces to columns. This is somewhat slow, so 
    // get_col_x should be used when possible
    // ----------------------------------------------------------
    
    Vec3 get_col(int i) const;

    // ----------------------------------------------------------
    // Retrieves the scaling of a matrix. This is expensive 
    // as three Vec3::norm() operations have to be performed
    // ----------------------------------------------------------

    gx::Vec3 get_scale() const;

    // ----------------------------------------------------------
    // Turns a matrix into its own transpose or inverse version. 
    // Both methods modify the object they are called on. To 
    // obtain a modified copy and leave the original object 
    // intact, use the copy ctor: 
    //   Mat3 inv = Mat3(m).invert();
    // ----------------------------------------------------------

    Mat3 & transpose();
    Mat3 & invert();

    // ----------------------------------------------------------
    // Normalizes the colums of this matrix. The result is that
    // the matrix becomes a pure rotation matrix
    // ----------------------------------------------------------
    
    Mat3 & normalize_cols();

    // ----------------------------------------------------------
    // Tells whether a matrix is a valid SR matrix. This is 
    // an expensive operation, so cache the result if possible
    // ----------------------------------------------------------
    
    bool is_SR_matrix() const;

    // ----------------------------------------------------------
    // Compares two matrices for equality
    // ----------------------------------------------------------

    bool equals(Mat3 const & other, Real tol = 1e-8) const;

    // ----------------------------------------------------------
    // Turn a matrix into a given transformation matrix. They all
    // modify an existing object. Use the no-init constructor
    // to obtain a new matrix: Mat3().make_rot(q);
    // To obtain a SR transformation in one line:
    //    Mat3().make_rot(q) * Mat3().make_scale(.5)
    // ----------------------------------------------------------

    // Rotation specified by a quaternion
    Mat3 & make_rot(Quat const & q);

    // Rotation specified by Euler angles
    Mat3 & make_rot(const Vec3 & xyz);
    
    // Rotation specified by an axis-angle
    Mat3 & make_rot(const AxisAngle & aa);

    // Uniform scaling
    Mat3 & make_scale(double s);

    // Non-uniform scaling
    Mat3 & make_scale(const Vec3 & s);

    // ----------------------------------------------------------
    // Compose two transformation matrices. The product has
    // the confusing property that R1 * R2 applies R2 first, and
    // then R1. Using concat() is therefore recommended because
    // the order of application is preserved: R1.concat(R2) is
    // equivalent to R2 * R1.
    // ----------------------------------------------------------

    Mat3 & concat(const Mat3 & second);
    friend Mat3 operator*(Mat3 const & a, Mat3 const & b);

    // ----------------------------------------------------------
    // Convert a rotation matrix to other representations of 
    // rotation. The matrix should be rotation-only (no scaling)
    // for these operations to work properly
    // ----------------------------------------------------------

    AxisAngle to_axis_angle() const;
    Quat to_quat() const;
    Vec3 to_euler() const;

    // ----------------------------------------------------------
    // Return a pointer to the first column of a matrix
    // ----------------------------------------------------------

    typedef Real Real3[3];
    const Real3 * to_2d_array() const;

    // ----------------------------------------------------------
    // Return a pointer to the first element of a matrix
    // ----------------------------------------------------------

    const Real * to_array() const;

    // --------------------------------------------------------------
    // Friend function that transforms a vector
    // --------------------------------------------------------------

    friend Vec3 operator*(const Mat3 & a, const Vec3 & v);

private:

    // ----------------------------------------------------------
    // Member variables. We make them private because individual 
    // columns should not be modified independently; it's better
    // to start from a valid SR matrix (i.e., the identity 
    // matrix) and then apply valid transformations to it
    // ----------------------------------------------------------
    
    Vec3 col_0, col_1, col_2;

    // Make Mat4 a friend to ease its implementation
    friend class Mat4; 
};

// --------------------------------------------------------------
// I/O functions to perform IO with a Mat3
// --------------------------------------------------------------

std::ostream & operator<<(std::ostream &os, Mat3 const & val);
std::istream &operator>>(std::istream &is, Mat3 & val);

// --------------------------------------------------------------
// Implementation of inlines and templates
// --------------------------------------------------------------

inline Mat3::Mat3() // Deliberately fails to initialize the object 
{ 
}

inline Mat3::Mat3(	double m00, double m01, double m02,
                    double m10, double m11, double m12, 
                    double m20, double m21, double m22)
: col_0(m00, m10, m20), 
  col_1(m01, m11, m21), 
  col_2(m02, m12, m22) 
{ 
}

inline Mat3::Mat3(Eye const) 
: col_0(1, 0, 0), 
  col_1(0, 1, 0),
  col_2(0, 0, 1)
{ 
}

inline Mat3::Mat3(const Vec3 &I, const Vec3 &J, const Vec3 &K)
:	col_0(I),
    col_1(J),
    col_2(K)
{ 
}

inline Vec3 Mat3::get_col_0() const 
{ 
    return col_0; 
}

inline Vec3 Mat3::get_col_1() const 
{ 
    return col_1; 
}

inline Vec3 Mat3::get_col_2() const 
{ 
    return col_2; 
}

inline Vec3 Mat3::get_col(int i) const 
{   
    if(i == 0)
    {
        return col_0;
    }
    if(i == 1)
    {
        return col_1;
    }
    if(i == 2)
    {
        return col_2;
    }
    throw std::exception();
}

inline const Mat3::Real3 * Mat3::to_2d_array() const 
{ 
    return (const Real3 *)col_0.to_array(); 
}

inline const Real * Mat3::to_array() const 
{ 
    return (const Real *)col_0.to_array(); 
}

} // namespace gx





